﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
using System.Xml;
using System.Diagnostics;

namespace TextAdornment
{
    public class IOService
    {
        #region Private Fields

        private string spcFilePath;
        private XmlDocument configDocument;

        #endregion

        #region Constructors

        public IOService()
        {
            configDocument = new XmlDocument();
        }

        #endregion

        #region Public Methods

        public static string GetFileContent(string codeFilePath)
        {
            string fileContent = string.Empty;
            using (var streamReader = new StreamReader(codeFilePath))
            {
                fileContent = streamReader.ReadToEnd();
            }
            return fileContent;
        }

        public static void DeleteFile(string filePath)
        {
            if (File.Exists(filePath))
            {
                File.Delete(filePath);
            }
        }

        public static string GetSPCFilePath(string templateFilePath)
        {
            //var spcFilePath = Path.ChangeExtension(templateFilePath, @".config");
            //return spcFilePath;
            var baseDirPath = Path.GetDirectoryName(templateFilePath);
            var childFilePaths = Directory.GetFiles(baseDirPath);

            foreach (var filePath in childFilePaths)
            {
                if (Path.GetExtension(filePath) == ".config")
                {
                    return filePath;
                }
            }

            return string.Empty;
        }

        public static string GetCodeFilePath(string spcFilePath)
        {
            // get the code file name from the content of the SPC file
            var codeFileName = GetCodeFileName(spcFilePath);

            // get the parent directory of the spc file
            var directoryPath = Path.GetDirectoryName(spcFilePath);

            // get the code file path
            var codeFilePath = Path.Combine(directoryPath, codeFileName);

            // return the code file path
            return codeFilePath;
        }

        public static string GetCodeFileName(string spcFilePath)
        {
            string codeFileName = string.Empty;
            var configFileXmlDoc = new XmlDocument();
            configFileXmlDoc.Load(spcFilePath);
            var xframeNodes = configFileXmlDoc.GetElementsByTagName("x-frame");
            for (int i = 0; i < xframeNodes.Count; i++)
            {
                var xframeNode = xframeNodes.Item(i);
                if (xframeNode.Attributes["outfile"] != null && xframeNode.Attributes["outfile"].Value != null)
                {
                    codeFileName = xframeNode.Attributes["outfile"].Value;
                    break;
                }
            }
            return codeFileName;
        }

        public static bool IsFileExisted(string spcFilePath)
        {
            return File.Exists(spcFilePath);
        }

        public void AddNewCodeOrVariable(string name, string value, ref Hashtable htbVariables)
        {
            // variable
            if (name.StartsWith("$"))
            {
                // create new instance for the variable
                var newVariable = new Variable()
                {
                    Name = name,
                    Content = value
                };

                // add the new variable to the hastable
                htbVariables.Add(name, newVariable);

                // add the new variable to the config file
                var xFrameNode = configDocument.GetElementsByTagName("x-frame").Item(0);
                var newNode = configDocument.CreateElement("set");
                var nodeNameAttribute = configDocument.CreateAttribute("var");
                nodeNameAttribute.Value = name;
                newNode.Attributes.Append(nodeNameAttribute);
                var nodeValueAttribute = configDocument.CreateAttribute("value");
                nodeValueAttribute.Value = value;
                newNode.Attributes.Append(nodeValueAttribute);
                xFrameNode.PrependChild(newNode);
            }
            // extra code
            else
            {
                // create new instance for the extra code
                var newExtraCode = new ExtraCode
                {
                    Name = name,
                    Content = value
                };

                // add the extra code to the hastable
                htbVariables.Add(name, newExtraCode);

                // add the extra code to the config file
                var xFrameNode = configDocument.GetElementsByTagName("adapt").Item(0);
                var newNode = configDocument.CreateElement("insert");
                var nodeNameAttribute = configDocument.CreateAttribute("break");
                nodeNameAttribute.Value = name;
                newNode.Attributes.Append(nodeNameAttribute);
                newNode.InnerText = value;
                xFrameNode.AppendChild(newNode);
            }

            // save changes
            configDocument.Save(spcFilePath);
        }

        public void LoadCodesAndVariables(string spcFilePath, ref Hashtable htbVariables)
        {
            // set the spc file path
            this.spcFilePath = spcFilePath;

            // Load the spc file into xml document
            configDocument.Load(spcFilePath);

            // Load variables into hashtable
            var variableList = configDocument.GetElementsByTagName("set");
            for (int i = 0; i < variableList.Count; i++)
            {
                var variable = variableList.Item(i);
                var variationPoint = new Variable
                {
                    Name = variable.Attributes["var"].Value,
                    Content = variable.Attributes["value"].Value
                };
                if (htbVariables.ContainsKey(variable.Attributes["var"].Value))
                {
                    htbVariables[variable.Attributes["var"].Value] = variationPoint;
                }
                else
                {
                    htbVariables.Add(variable.Attributes["var"].Value, variationPoint);
                }
            }

            // Load extra codes into hashtable
            var breakList = configDocument.GetElementsByTagName("insert");
            for (int i = 0; i < breakList.Count; i++)
            {
                var breakPoint = breakList.Item(i);
                var variationPoint = new ExtraCode
                {
                    Name = breakPoint.Attributes["break"].Value,
                    Content = breakPoint.InnerText.Trim()
                };
                if (htbVariables.ContainsKey(breakPoint.Attributes["break"].Value))
                {
                    htbVariables[breakPoint.Attributes["break"].Value] = variationPoint;
                }
                else
                {
                    htbVariables.Add(breakPoint.Attributes["break"].Value, variationPoint);
                }
            }
        }

        /// <summary>
        /// Assume that the XML config file has been loaded
        /// </summary>
        /// <param name="variableName"></param>
        /// <param name="value"></param>
        public void UpdateVariableValue(string variableName, string value)
        {
            // get the list of variables
            var variableList = configDocument.GetElementsByTagName("set");

            // update specified variable
            for (int i = 0; i < variableList.Count; i++)
            {
                var variable = variableList.Item(i);
                
                if (variable.Attributes["var"].Value == variableName)
                {
                    variable.Attributes["value"].Value = value.Trim();
                    break;
                }
            }

            // save changes to the config file
            configDocument.Save(spcFilePath);
        }

        /// <summary>
        /// Assume that the XML config file has been loaded
        /// </summary>
        /// <param name="breakPointName"></param>
        /// <param name="code"></param>
        public void UpdateCode(string breakPointName, string code)
        {
            // get the list of break points
            var breakPointList = configDocument.GetElementsByTagName("insert");

            // update specified variable
            for (int i = 0; i < breakPointList.Count; i++)
            {
                var breakPoint = breakPointList.Item(i);

                if (breakPoint.Attributes["break"].Value == breakPointName)
                {
                    breakPoint.InnerText = code.Trim();
                    break;
                }
            }

            // save changes to the config file
            configDocument.Save(spcFilePath);
        }

        public static string GetFullPath(string relativePath)
        {
            // get application data folder path
            string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);

            // create the full path from relative path
            string fullPath = Path.Combine(appDataPath, relativePath);

            // return the full path
            return fullPath;
        }

        public static void GenerateCodeFromTempate(string spcFilePath)
        {
            // get the path to XVCL processor
            var xvclProcessorFilePath = GetXVCLProcessorFilePath();

            // specify file name and arguments
            var fileName = @"java.exe";
            var arguments = @"-jar " + @"""" + xvclProcessorFilePath + @"""" + " -B " + @"""" + spcFilePath + @"""";

            // execute the code generation command
            ExecuteCommand(fileName, arguments);
        }

        private static string GetXVCLProcessorFilePath()
        {
            return AppDomain.CurrentDomain.BaseDirectory + @"XVCL\xvcl.jar";
        }

        private static void ExecuteCommand(string fileName, string arguments)
        {
            // create process info based on the parameters
            var processInfo = new ProcessStartInfo(fileName, arguments)
            {
                CreateNoWindow = true,
                UseShellExecute = false,
                WindowStyle = ProcessWindowStyle.Hidden
            };

            // create new process with the above information
            var process = new Process()
            {
                StartInfo = processInfo
            };

            // start the process
            process.Start();
            process.WaitForExit();
        }

        public string GetFileExtension(string filePath)
        {
            return Path.GetExtension(filePath);
        }

        public void CleanUpHashtable(ref Hashtable htbVariables, string templateFileContent)
        {
            // go through the hashtable to remove unnecessary variables/extra codes
            foreach (var key in htbVariables.Keys)
            {
                if (!templateFileContent.Contains("@" + key.ToString() + "@"))
                {
                    // remove from hashtable
                    htbVariables.Remove(key);

                    // remove from config file
                    var xframeNode = configDocument.GetElementsByTagName("x-frame").Item(0);
                    XmlNodeList nodeList;
                    if (key.ToString().StartsWith("$")) // variable
                    {
                        nodeList = configDocument.GetElementsByTagName("set");
                        foreach (var node in nodeList)
                        {
                            if ((node as XmlNode).Attributes["var"].Value == key.ToString())
                            {
                                RemoveXMLNode(node as XmlNode);
                                break;
                            }
                        }
                    }
                    else // code
                    {
                        nodeList = configDocument.GetElementsByTagName("insert");
                        foreach (var node in nodeList)
                        {
                            if ((node as XmlNode).Attributes["break"].Value == key.ToString())
                            {
                                RemoveXMLNode(node as XmlNode);
                                break;
                            }
                        }
                    }
                }
            }
        }

        private void RemoveXMLNode(XmlNode node)
        {
            if (node.ParentNode != null)
            {
                node.ParentNode.RemoveChild(node);
            }
            else
            {
                configDocument.RemoveChild(node);
            }
        }

        public static bool IsInstanceTemplate(string templateFilePath)
        {
            // get the base directory to the template file
            var baseDirPath = Path.GetDirectoryName(templateFilePath);

            // validate against some conditions
            var baseDir = new DirectoryInfo(baseDirPath);
            var childDir = baseDir.GetDirectories();
            var firstConditions = (childDir.Length == 0) || (childDir.Length == 1 && childDir[0].Name.Contains(".xvcl"));
            var childFiles = baseDir.GetFiles();
            var secondConditions = true;
            foreach (var childFile in childFiles)
            {
                if (childFile.Name.Contains("template.manifest"))
                {
                    secondConditions = false;
                }
            }
            return firstConditions && secondConditions;
        }

        #endregion
    }
}
